prepare() does the expensive one-time work — segment text, measure with canvas, cache. layout() is cheap arithmetic over those cached widths.
The whole point: measurement never touches the DOM. No getBoundingClientRect, no forced reflow, no flash.
A PreparedText is the only field pretext touches on a caption. Carry it wherever the content goes; layout() will not need anything else.
Not in callbacks. Not in closures. A plain boolean the render loop reads and clears. That's what makes composition of events possible.
Each cached node gets a comment describing when you'd evict it. Update the comment before the code when the render pattern changes; the shape will follow.
Pretext measures with canvas using this exact string. Out of sync with CSS = subtle mis-measurement, page flash, an afternoon of debugging. One source of truth, here.
Never call prepare() inside the render loop. Do it when content is born. If text later changes, rebuild that one handle.
given is what callers may pass; the bare lines are facts freerange must prove from the body below.
No caption dropped (rows.length == captions.length). Rows flow downward (nondecreasing(y)). Nothing overflows the right margin. Break one, the build fails — long before a screenshot would.
Math.min(MAX_COL, …) is what lets the prover discharge columnWidth: 220..320. Remove the clamp, lose the proof.
Pure arithmetic over prepared measurements. No DOM, no reflow. Call every frame as many times as you need.
Everything dynamic lives in st: content, viewport, events, animation clock. Single global state is a feature — serializable, cloneable, rewindable.
A dozen events in a single frame collapse into one render. The guard if (scheduledRaf != null) return is the entire scheduler.
The handler stores one fact and schedules. No logic in the callback. Every decision lands in render(), where all of the state is visible together.
reads → inputs → layout → animation tick → commit → writes. Phases never interleave. DOM reads never sit next to DOM writes.
Reset every ephemeral event flag in the commit phase. Skip it, and the next frame thinks the same resize is still fresh — a loop is born.
className and textContent are set inside the create block, never touched again. Put anything constant here.
transform, width, height are written every frame from the layout result. One pass, in order, after layout has decided every number.